package world.square;

import java.math.BigDecimal;
import java.math.RoundingMode;

import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Immutable;
import be.kuleuven.cs.som.annotate.Raw;
import be.kuleuven.cs.som.annotate.Value;


/**
 * Enumeration class representing different temperature scales.
 * Currently supports Celcius, Kelvin and Fahrenheit.
 * 
 * @version  1.0
 * @author   Wim Marynissen, Frederic Mes
 */

@Value
public enum TemperatureScale {
	
	CELCIUS('C'),KELVIN('K'),FAHRENHEIT('F');
	
	/**
	 *	Variable containing the symbol of this temperature scale.
	 */
	private char scaleSymbol;
	
	/**
     * Initialisation of the temperature scale
     *
     * @param	scaleSymbol
     *      	The symbol of this temperature scale
     * @post	The symbol of this scale is equal to the specified symbol
     *     		| new.getSymbol() == symbol
     */

	private TemperatureScale(char scaleSymbol){ 
		this.scaleSymbol=scaleSymbol;
	}
	
	/**
     * Returns the temperature scale's symbol, being the first letter of the scale name.
     * @return	the symbol of this temperature scale
     *     		| if (this == TemperatureScale.CELCIUS) 
     *         	|               return "C"
     *       	| else if (this == TemperatureScale.FAHRENHEIT) 
     *    	  	|               return "F"
     *      	| else if (this == TemperatureScale.KELVIN) 
     *      	|               return "K"
     */
	@Basic @Raw @Immutable
	public char getScaleSymbol() { 
		return scaleSymbol;
	}
	
	/**
     * @param 	value
     *       	The temperature value in the current scale
     * @param  	newScale
     *      	The scale to convert the temperature value to
     * @return  The temperature value in the specified scale
     *          | let 
     *          | conversion = this.getConversionConstants(newScale)
     *          | in
     *          | result == value.multiply(conversion[0]).add(conversion[1])
     * @throws	IllegalArgumentException
     * 			The new scale is non-effective.
     * 			| (newScale == null)
     */

	public BigDecimal convertToScale(BigDecimal value, TemperatureScale newScale) throws IllegalArgumentException{
		if (newScale == null)
			throw new IllegalArgumentException("Illegal scale!");
		BigDecimal[] conversion = getConversionConstants(newScale);  
		return value.multiply(conversion[0]).add(conversion[1]);
	}
	
    /**
     * @param 	otherScale
     * 			The temperature scale to convert to.
     * @return	The result is an array of BigDecimals
     * 			| result instanceof BigDecimal[]
     * @return  The elements of the array are effective
     *          | (result[0]!=null) && (result[1]!=null)
     * @return	If the temperature scale is equal to the current one, 1 and 0 are returned.
     * 			| if (this == other)
     *          | then result == {BigDecimal.ONE,BigDecimal.ZERO)
     * @return  The array numbers are the inverse of the opposite rescale (from otherScale to this scale).
     *          | result == {BigDecimal.ONE.divide(conversionFactors[otherScale.ordinal()][this.ordinal()][0],4,RoundingMode.HALF_UP),
     *          |				(other.toTemperatureUnit(this)[1].negate()).divide(other.toTemperatureUnit(this)[0],4,RoundingMode.HALF_UP)}
     * @return  Conversion numbers have a scale of 4, rounded HALF_UP.
     *          | if (this != other)
     *          | then (result[0].scale() == 4) && (result[1].scale() == 4)	
     * @throws 	IllegalArgumentException
     * 			The specified temperature scale is non-effective.
     * 			| (otherScale == null)
     * 			
     */
    public BigDecimal[] getConversionConstants(TemperatureScale otherScale) throws IllegalArgumentException{
    	if (otherScale == null)
    	      throw new IllegalArgumentException("Non effective temperature scale!");
    	else if ((conversionFactors[this.ordinal()][otherScale.ordinal()][0] == null) && (conversionFactors[this.ordinal()][otherScale.ordinal()][1] == null)){
                conversionFactors[this.ordinal()][otherScale.ordinal()][0] = 
                	BigDecimal.ONE.divide(conversionFactors[otherScale.ordinal()][this.ordinal()][0],4,RoundingMode.HALF_UP);
                conversionFactors[this.ordinal()][otherScale.ordinal()][1] =
                	(conversionFactors[otherScale.ordinal()][this.ordinal()][1].negate())
                	.divide(conversionFactors[otherScale.ordinal()][this.ordinal()][0],4,RoundingMode.HALF_UP);
        }
        return conversionFactors[this.ordinal()][otherScale.ordinal()];
	}
	
	/**
	 *	Threedimensional array containing the factor to multiply with and the value to add,
	 *	in order to change from one temperature scale to another.
	 */
	private static BigDecimal[][][] conversionFactors = new BigDecimal[3][3][2];


	static {
        conversionFactors[CELCIUS.ordinal()][CELCIUS.ordinal()][0] =
                BigDecimal.ONE;
        conversionFactors[CELCIUS.ordinal()][KELVIN.ordinal()][0] =
                BigDecimal.ONE;
        conversionFactors[CELCIUS.ordinal()][FAHRENHEIT.ordinal()][0] =
                new BigDecimal("1.8");      
        conversionFactors[CELCIUS.ordinal()][CELCIUS.ordinal()][1] =
                BigDecimal.ZERO;
        conversionFactors[CELCIUS.ordinal()][KELVIN.ordinal()][1] =
                new BigDecimal("273.15");       
        conversionFactors[CELCIUS.ordinal()][FAHRENHEIT.ordinal()][1] =
                new BigDecimal("32");
        
        conversionFactors[KELVIN.ordinal()][KELVIN.ordinal()][0] =
            BigDecimal.ONE;
        conversionFactors[KELVIN.ordinal()][FAHRENHEIT.ordinal()][0] =
            new BigDecimal("1.8");
        conversionFactors[KELVIN.ordinal()][KELVIN.ordinal()][1] =
                BigDecimal.ZERO;
        conversionFactors[KELVIN.ordinal()][FAHRENHEIT.ordinal()][1] =
                new BigDecimal("459.67").negate();
        
        conversionFactors[FAHRENHEIT.ordinal()][FAHRENHEIT.ordinal()][0] =
            BigDecimal.ONE;
        conversionFactors[FAHRENHEIT.ordinal()][FAHRENHEIT.ordinal()][1] =
                BigDecimal.ZERO;
	}

}
